package com.smart.web.aop;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.smart.model.User;
import com.smart.util.JSON;

import jodd.util.StringUtil;

//@Aspect
//@Controller
public class AccessLogAspect {
	
	private static final String HEADER_USER_AGENT = "user-agent";

	private final Logger LOG = LoggerFactory.getLogger(getClass());

	@Pointcut("execution(* com.smart..*Controller.*(..))")
	public void controllerAspect() {

	}

	@SuppressWarnings("unchecked")
	@Around("controllerAspect()")
	public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
		try {
			HttpServletRequest req = getRequest();
			HttpSession session = req.getSession();
			User user = (User)session.getAttribute("user");
			String username = "";
			if(user==null) {
				username = "guest";
			}
			else {
				username = user.getUsername();
			}
			String uri = req.getRequestURL().toString();
			String query = req.getQueryString();
			if(StringUtil.isNotBlank(query)) {
				uri += "?" + query;
			}
			String httpMethod = req.getMethod();
			String ip = getRemoteIp(req);
			String ua = req.getHeader(HEADER_USER_AGENT).toLowerCase();
			
			Signature signature = joinPoint.getSignature();
			MethodSignature methodSignature = (MethodSignature) signature;
			Method targetMethod = methodSignature.getMethod();
			Class targetClass = joinPoint.getTarget().getClass();
			Method realMethod = targetClass.getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
			

			Parameter[] parameters = realMethod.getParameters();

			Object[] args = joinPoint.getArgs();
			Annotation[][] annotations = realMethod.getParameterAnnotations();
			List simpleParamList = new ArrayList();
			Object filteredArgs = null;
			try{
				int argIndex = -1;
				for(int i=0; i<annotations.length; i++) {
					for(int j=0; j<annotations[i].length; j++) {
						if(annotations[i][j].annotationType().equals(RequestBody.class)) {
							argIndex = i;
						}
					}
				}
				
				if(argIndex >=0) {
					filteredArgs = args[argIndex];
				}
				else {
					for(int i=0; i<args.length; i++) {
						Class paramClz = args[i].getClass();
						if(BeanUtils.isSimpleValueType(args[i].getClass())) {
							simpleParamList.add(args[i]);
						}
						else if(args[i] instanceof User) {
							User cloned = new User();
							BeanUtils.copyProperties(args[i], cloned);
							//cloned.setPassword("******");
							simpleParamList.add(cloned);
						}
						else if(paramClz.getName().startsWith("com.smart.model")) {
							//LOG.info("args[{}]: {}", i, JSON.getDefault().toJSONString(args[i]));
							simpleParamList.add(args[i]);
						}
					}
	
					filteredArgs = simpleParamList;
				}
			}
			catch(Exception ee) {
				LOG.error("exception occurred when analyzing parameters");
			}
			LOG.info("{} {} - {} - {} enters {}.{}() with parameters {}, user-agent {}",
					new Object[]{httpMethod, uri, ip, username, targetClass.getSimpleName(), realMethod.getName(), JSON.getDefault().toJSONString(filteredArgs), ua});
		}
		catch(Throwable e) {
			LOG.error("exception occurred, ", e);
		}
		return joinPoint.proceed();
	}
	
	private static HttpServletRequest getRequest(){
		HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		return req;
	}

	// get the real IP address under complicated network conditions
	private String getRemoteIp(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}

}
